﻿
// 2020/8/26: 首个记录（根据env-sample-v2.h 2020/5/15版本修改）
// 2020/11/3: 新增气压、相对湿度、能见度、风向、风速、PM10、道路状况、车道交通状况、灾害情况等字段。道路交通状况更名
// 2020/11/12: 支持Spadas7
// 2020/11/26: 实现fromGeneralSample
// 2022/11/2: 支持Spadas8.4
// 2023/8/17: 新增瞬时雨量字段
// 2023/10/10: 补充部分英文注释
// 2023/10/24: 新增字段（累计雨量、精确风向、总辐射强度、紫外线辐射强度、臭氧浓度、一氧化碳浓度、二氧化硫浓度、二氧化氮浓度）
// 2024/7/3: 基于新样本框架。枚举改为使用enum class
// 2025/3/25: 实现interpolate
// 2025/8/4: 改为继承SessionSample。改回单头文件

#ifndef ENV_SAMPLE_V3_H
#define ENV_SAMPLE_V3_H

#include "spadas.h"

namespace env_sample_v3
{
	using namespace spadas;

	enum class WeatherType
	{
		Unknown = 0, // 未知
		Sunny = 1, // 晴
		Cloudy = 2, // 阴
		Rainy = 3, // 雨
		Snowy = 4, // 雪
		Foggy = 5, // 雾
		Sand = 6, // 沙尘
	};

	enum class RoadType
	{
		Unknown = 0, // 未知
		Highway = 1, // Without traffic light / 高速路，无红绿灯
		CityExpress = 2, // Without traffic light / 城市快速路，无红绿灯
		CityMain = 3, // With traffic light / 城市主干道，有红绿灯
		GeneralRoad = 4, // With traffic light / 一般道路，有红绿灯
		CountryRoad = 5, // Without traffic light / 乡村道路，无红绿灯
	};

	enum class SpotType
	{
		Unknown = 0, // 未知
		TollGate = 1, // 收费站
		ServiceArea = 2, // 服务区
		Tunnel = 3, // 隧道
		Accident = 4, // 事故
		Crossing = 5, // Intersection / 路口
		Roundabout = 7, // 环岛
		HighwayEntrance = 8, // 高速入口
		HighwayExit = 9, // 高速出口
		Bridge = 10, // 桥
		ExpressEntrance = 11, // 快速路入口
		ExpressExit = 12, // 快速路出口
	};

	struct Spot
	{
		SpotType spotType; // 场所类型
		Double distance; // [m] Distance between the spot and subject vehicle / 本车到场所的距离
	};

	enum class TrafficStatus
	{
		Unknown = 0, // 未知
		Open = 1, // 畅通
		Slow = 2, // 缓慢
		Crowded = 3, // 拥挤
		Reverse = 4, // Only for adjacent lane traffic / 逆向（仅限相邻车道交通状况）
	};

	enum class WindDirection
	{
		Unknown = 0, // 未知
		East = 1, // 东风
		South = 2, // 南风
		West = 3, // 西风
		North = 4, // 北风
		SouthEast = 5, // 东南风
		NorthEast = 6, // 东北风
		SouthWest = 7, // 西南风
		NorthWest = 8, // 西北风
	};

	enum class RoadSurfaceStatus
	{
		Unknown = 0, // 未知
		Dry = 1, // 干燥
		Wet = 2, // 湿滑
		SnowCovered = 3, // 积雪
		Frozen = 4, // 结冰
	};

	enum class DisasterStatus
	{
		Unknown = 0, // 未知
		No = 1, // 无灾害
		Yes = 2, // 有灾害
	};

	struct EnvSampleData
	{
		WeatherType weatherType; // 天气类型
		OptionalDouble temperature; // [°C] 温度
		OptionalDouble humidity; // [%] Relative humidity / 相对湿度
		OptionalDouble pressure; // [mb] Atmospheric pressure / 气压
		OptionalDouble pm25; // [ug/m3] PM 2.5
		OptionalDouble pm10; // [ug/m3] PM 10
		OptionalDouble o3Concentration; // [ppb] Ozone concentration / 臭氧浓度
		OptionalDouble coConcentration; // [ppb] Carbon monoxide concentration / 一氧化碳浓度
		OptionalDouble so2Concentration; // [ppb] Sulfur dioxide concentration / 二氧化硫浓度
		OptionalDouble no2Concentration; // [ppb] Nitrogen dioxide concentration / 二氧化氮浓度
		OptionalDouble visibility; // [KM] 能见度
		OptionalDouble illumination; // [lux] 光照强度
		OptionalDouble totalRadiationIntensity; // [W/m2] 总辐射强度
		OptionalDouble ultravioletRadiationIntensity; // [W/m2] 紫外线辐射强度
		OptionalDouble rainfall; // [mm/min] Instantaneous rainfall / 瞬时雨量
		OptionalDouble cumulativeRainfall; // [mm] 累计雨量
		OptionalDouble windSpeed; // [m/s] 风速
		OptionalDouble accurateWindDirection; // [deg] Accurate wind direction (from), 0 is north wind, CCW is positive, ranges -180~180 / 北风为0，CCW为正，范围在-180～180度
		WindDirection windDirection; // Coarse wind direction (from) / 大致风向
		DisasterStatus typhoonStatus; // 台风状况
		DisasterStatus tornadoStatus; // 龙卷风状况
		DisasterStatus lightningStatus; // 雷电状况
		DisasterStatus hailStatus; // 冰雹状况

		String roadName; // 道路名称
		OptionalInt speedLimit; // [KPH] 道路限速
		RoadType roadType; // 道路类型
		RoadSurfaceStatus roadSurfaceStatus; // 路面状况
		TrafficStatus roadTraffic; // 道路交通状况
		TrafficStatus currentLaneTraffic; // 本车道交通状况
		TrafficStatus leftLaneTraffic; // 左车道交通状况
		TrafficStatus rightLaneTraffic; // 右车道交通状况
		Array<Spot> spots; // 场所列表

		EnvSampleData()
		{
			weatherType = WeatherType::Unknown;
			windDirection = WindDirection::Unknown;
			typhoonStatus = DisasterStatus::Unknown;
			tornadoStatus = DisasterStatus::Unknown;
			lightningStatus = DisasterStatus::Unknown;
			hailStatus = DisasterStatus::Unknown;
			roadType = RoadType::Unknown;
			roadSurfaceStatus = RoadSurfaceStatus::Unknown;
			roadTraffic = TrafficStatus::Unknown;
			currentLaneTraffic = TrafficStatus::Unknown;
			leftLaneTraffic = TrafficStatus::Unknown;
			rightLaneTraffic = TrafficStatus::Unknown;
		}
	};

	class EnvSample : public SessionSample
	{
	private:
		class EnvSampleVars : public SessionSampleVars
		{
		public:
			SPADAS_VARS(EnvSample, SessionSampleVars)
			EnvSampleData data;

			Bool supportInterpolation() override
			{
				return TRUE;
			}

			SessionGeneralSample toGeneralSample()
			{
				SessionGeneralSample sample;
				sample.timestamp() = timestamp;

				Array<GeneralElement> values(64 + data.spots.size() * 2);
				sample.values() = values;

				values[1] = data.spots.size();

				values[2] = (UInt)data.weatherType;
				values[3] = data.temperature;
				values[4] = data.pm25;
				values[5] = data.speedLimit.valid ? data.speedLimit.value : OptionalDouble();
				values[6] = (UInt)data.roadType;
				values[7] = data.illumination;
				values[8] = data.windSpeed;
				values[9] = (UInt)data.roadTraffic;
				values[10] = data.roadName;
				values[11] = data.pressure;
				values[12] = data.humidity;
				values[13] = data.visibility;
				values[14] = (UInt)data.windDirection;
				values[15] = data.pm10;
				values[16] = (UInt)data.roadSurfaceStatus;
				values[17] = (UInt)data.currentLaneTraffic;
				values[18] = (UInt)data.leftLaneTraffic;
				values[19] = (UInt)data.rightLaneTraffic;
				values[20] = (UInt)data.typhoonStatus;
				values[21] = (UInt)data.tornadoStatus;
				values[22] = (UInt)data.lightningStatus;
				values[23] = (UInt)data.hailStatus;
				values[24] = data.rainfall;
				values[25] = data.cumulativeRainfall;
				values[26] = data.accurateWindDirection;
				values[27] = data.totalRadiationIntensity;
				values[28] = data.ultravioletRadiationIntensity;
				values[29] = data.o3Concentration;
				values[30] = data.coConcentration;
				values[31] = data.so2Concentration;
				values[32] = data.no2Concentration;

				for (UInt i = 0; i < data.spots.size(); i++)
				{
					values[64 + 2 * i] = (UInt)data.spots[i].spotType;
					values[64 + 2 * i + 1] = data.spots[i].distance;
				}

				sample.significantCount() = values.size();
				return sample;
			}

			Bool fromSample(String protocol, SessionSample sample) override
			{
				SessionGeneralSample generalSample;
				if (!sample.is<SessionGeneralSample>(generalSample)) return FALSE;

				timestamp = generalSample.timestamp();
				auto values = generalSample.values();

				if (protocol == "env-sample-v3")
				{
					if (values.size() < 2) return FALSE;

					UInt roadNameBinSize = (UInt)values[0].value;
					UInt spotsCount = (UInt)values[1].value;
					if (values.size() != 64 + spotsCount * 2 + roadNameBinSize) return FALSE;

					data.weatherType = values[2].valid ? (WeatherType)(Int)values[2].value : WeatherType::Unknown;
					data.temperature = values[3];
					data.pm25 = values[4];
					data.speedLimit = values[5].valid ? (Int)values[5].value : OptionalInt();
					data.roadType = values[6].valid ? (RoadType)(Int)values[6].value : RoadType::Unknown;
					data.illumination = values[7];
					data.windSpeed = values[8];
					data.roadTraffic = values[9].valid ? (TrafficStatus)(Int)values[9].value : TrafficStatus::Unknown;
					data.pressure = values[11];
					data.humidity = values[12];
					data.visibility = values[13];
					data.windDirection = values[14].valid ? (WindDirection)(Int)values[14].value : WindDirection::Unknown;
					data.pm10 = values[15];
					data.roadSurfaceStatus = values[16].valid ? (RoadSurfaceStatus)(Int)values[16].value : RoadSurfaceStatus::Unknown;
					data.currentLaneTraffic = values[17].valid ? (TrafficStatus)(Int)values[17].value : TrafficStatus::Unknown;
					data.leftLaneTraffic = values[18].valid ? (TrafficStatus)(Int)values[18].value : TrafficStatus::Unknown;
					data.rightLaneTraffic = values[19].valid ? (TrafficStatus)(Int)values[19].value : TrafficStatus::Unknown;
					data.typhoonStatus = values[20].valid ? (DisasterStatus)(Int)values[20].value : DisasterStatus::Unknown;
					data.tornadoStatus = values[21].valid ? (DisasterStatus)(Int)values[21].value : DisasterStatus::Unknown;
					data.lightningStatus = values[22].valid ? (DisasterStatus)(Int)values[22].value : DisasterStatus::Unknown;
					data.hailStatus = values[23].valid ? (DisasterStatus)(Int)values[23].value : DisasterStatus::Unknown;
					data.rainfall = values[24];
					data.cumulativeRainfall = values[25];
					data.accurateWindDirection = values[26];
					data.totalRadiationIntensity = values[27];
					data.ultravioletRadiationIntensity = values[28];
					data.o3Concentration = values[29];
					data.coConcentration = values[30];
					data.so2Concentration = values[31];
					data.no2Concentration = values[32];

					data.spots = Array<Spot>(spotsCount);
					for (UInt i = 0; i < data.spots.size(); i++)
					{
						data.spots[i].spotType = (SpotType)(Int)values[64 + 2 * i].value;
						data.spots[i].distance = values[64 + 2 * i + 1].value;
					}

					if (roadNameBinSize > 0)
					{
						Binary roadNameBin(roadNameBinSize);
						for (UInt i = 0; i < roadNameBinSize; i++)
						{
							roadNameBin[i] = (Byte)values[64 + data.spots.size() * 2 + i].value;
						}
						data.roadName = roadNameBin;
					}
					else data.roadName = String();

					if (data.roadName.isEmpty() && values[10].valid && values[10].isText) data.roadName = values[10].text;

					return TRUE;
				}
				else if (protocol == "env-sample-v2")
				{
					if (values.size() < 2) return FALSE;

					UInt roadNameBinSize = (UInt)values[0].value;
					UInt spotsCount = (UInt)values[1].value;
					if (values.size() != 10 + spotsCount * 2 + roadNameBinSize) return FALSE;

					data.weatherType = values[2].valid ? (WeatherType)(Int)values[2].value : WeatherType::Unknown;
					data.temperature = values[3];
					data.pm25 = values[4];
					data.speedLimit = values[5].valid ? (Int)values[5].value : OptionalInt();
					data.roadType = values[6].valid ? (RoadType)(Int)values[6].value : RoadType::Unknown;
					data.illumination = values[7];
					data.windSpeed = values[8];
					data.roadTraffic = values[9].valid ? (TrafficStatus)(Int)values[9].value : TrafficStatus::Unknown;

					data.spots = Array<Spot>(spotsCount);
					for (UInt i = 0; i < data.spots.size(); i++)
					{
						data.spots[i].spotType = (SpotType)(Int)values[10 + 2 * i].value;
						data.spots[i].distance = values[10 + 2 * i + 1].value;
					}

					if (roadNameBinSize > 0)
					{
						Binary roadNameBin(roadNameBinSize);
						for (UInt i = 0; i < roadNameBinSize; i++)
						{
							roadNameBin[i] = (Byte)values[10 + data.spots.size() * 2 + i].value;
						}
						data.roadName = roadNameBin;
					}
					else data.roadName = String();

					return TRUE;
				}

				return FALSE;
			}

			SessionSample interpolate(SessionSample& ss1, Double w1, SessionSample& ss2, Double w2, FullTimestamp timestamp) override
			{
				EnvSample es1, es2;
				if (!ss1.is<EnvSample>(es1) || !ss2.is<EnvSample>(es2)) return SessionSample();

				EnvSampleData& s1 = es1.var()->data;
				EnvSampleData& s2 = es2.var()->data;

				EnvSample es;
				es.timestamp() = timestamp;

				EnvSampleData& so = es.var()->data;
				so.weatherType = w1 > w2 ? s1.weatherType : s2.weatherType;
				so.temperature = s1.temperature * w1 + s2.temperature * w2;
				so.pm25 = s1.pm25 * w1 + s2.pm25 * w2;
				so.speedLimit = w1 > w2 ? s1.speedLimit : s2.speedLimit;
				so.roadType = w1 > w2 ? s1.roadType : s2.roadType;
				so.illumination = s1.illumination * w1 + s2.illumination * w2;
				so.windSpeed = s1.windSpeed * w1 + s2.windSpeed * w2;
				so.roadTraffic = w1 > w2 ? s1.roadTraffic : s2.roadTraffic;
				so.roadName = w1 > w2 ? s1.roadName : s2.roadName;
				so.pressure = s1.pressure * w1 + s2.pressure * w2;
				so.humidity = s1.humidity * w1 + s2.humidity * w2;
				so.visibility = s1.visibility * w1 + s2.visibility * w2;
				so.windDirection = w1 > w2 ? s1.windDirection : s2.windDirection;
				so.pm10 = s1.pm10 * w1 + s2.pm10 * w2;
				so.roadSurfaceStatus = w1 > w2 ? s1.roadSurfaceStatus : s2.roadSurfaceStatus;
				so.currentLaneTraffic = w1 > w2 ? s1.currentLaneTraffic : s2.currentLaneTraffic;
				so.leftLaneTraffic = w1 > w2 ? s1.leftLaneTraffic : s2.leftLaneTraffic;
				so.rightLaneTraffic = w1 > w2 ? s1.rightLaneTraffic : s2.rightLaneTraffic;
				so.typhoonStatus = w1 > w2 ? s1.typhoonStatus : s2.typhoonStatus;
				so.tornadoStatus = w1 > w2 ? s1.tornadoStatus : s2.tornadoStatus;
				so.lightningStatus = w1 > w2 ? s1.lightningStatus : s2.lightningStatus;
				so.hailStatus = w1 > w2 ? s1.hailStatus : s2.hailStatus;
				so.rainfall = s1.rainfall * w1 + s2.rainfall * w2;
				so.cumulativeRainfall = s1.cumulativeRainfall * w1 + s2.cumulativeRainfall * w2;
				so.accurateWindDirection = s1.accurateWindDirection * w1 + s2.accurateWindDirection * w2;
				so.totalRadiationIntensity = s1.totalRadiationIntensity * w1 + s2.totalRadiationIntensity * w2;
				so.ultravioletRadiationIntensity = s1.ultravioletRadiationIntensity * w1 + s2.ultravioletRadiationIntensity * w2;
				so.o3Concentration = s1.o3Concentration * w1 + s2.o3Concentration * w2;
				so.coConcentration = s1.coConcentration * w1 + s2.coConcentration * w2;
				so.so2Concentration = s1.so2Concentration * w1 + s2.so2Concentration * w2;
				so.no2Concentration = s1.no2Concentration * w1 + s2.no2Concentration * w2;
				so.spots = w1 > w2 ? s1.spots : s2.spots;

				return es.as<SessionSample>();
			}
		};

	public:
		SPADAS_CLASS("env_sample_v3.EnvSample", EnvSampleVars)

		EnvSample() : SessionSample(new EnvSampleVars)
		{}
		EnvSampleData *operator ->()
		{
			return &var()->data;
		}

		static String getProtocolName()
		{
			return "env-sample-v3";
		}
		SessionGeneralSample toGeneralSample()
		{
			return var()->toGeneralSample();
		}

		static Array<String> getProtocolNames()
		{
			return Array<String>::create(2, "env-sample-v2", "env-sample-v3");
		}
		Bool fromGeneralSample(String protocol, SessionGeneralSample generalSample)
		{
			return fromSample(protocol, generalSample);
		}
	};
}

#endif
